Javascript에서의 비동기 함수
자바스크립트는 기본적으로 '단일 스레드'언어이다. 이는 한 번에 하나의 작업만 수행할 수 있다는 의미이다. 그러나 웹 애플리케이션에서는 데이터 가져오기, 파일 읽기/쓰기 등 시간이 오래 걸리는 작업이 많이 있다. 이러한 작업을 동기적으로 처리한다면, 작업이 완료될 때까지 다른 모든 코드의 실행이 중단된다.
비동기 프로그래밍은 이런 문제를 해결한다. 비동기 작업은 메인 실행 스레드를 차단하지 않고 백그라운드에서 실행되며, 작업이 완료되면 결과를 반환한다.
"단일 스레드" 라는것은 어디까지나 코드를 읽고 해석하고 실행시키는 작업만 해당된다. 자바스크립트 엔진은 실행 자체는 Node.js 나 브라우저에 업무를 위임하게 되는데 Node.js 나 브라우저는 멀티 스레드로 컴퓨팅하여 작업을 처리한다.
비동기 처리 방식의 발전#
콜백 함수(Callbacks)#
초기 자바스크립트에서는 비동기 처리를 위해 콜백 함수를 사용했다. 콜백 함수는 다른 함수에 인수로 전달되어, 그 함수 내부에서 나중에 호출되는 함수를 의미한다. 비동기 처리에서는 특정 작업(예: 타이머 완료, 데이터 로딩 완료, 이벤트 발생)이 끝났을 때 실행될 로직을 콜백 함수 형태로 전달하는 방식으로 주로 사용된다. 비동기 작업을 시작하는 함수는 즉시 반환되고, 작업이 완료되면 미리 등록된 콜백 함수가 호출되어 결과를 처리하거나 다음 단계를 진행한다.
JavaScriptjs
콜백 지옥(Callback Hell)#
여러 개의 비동기 작업을 순차적으로 처리해야 할 때 콜백 함수가 계속해서 중첩되어 코드의 들여쓰기 수준이 깊어지고 가독성이 극도로 나빠지는 상황을 말한다.
콜백 지옥은 콜백 패턴의 한계를 보여주는 대표적인 예시이며 이러한 문제를 해결하기 위해 promise
와 async/await
같은 더 나은 비동기 처리 패턴이 등장하게 되었다.
문제점
- 가독성 저하: 코드가 오른쪽으로 계속 길어져 전체 로직을 파악하기 어렵다.
- 에러 처리의 어려움: 각 콜백 단계마다 에러 처리를 별도로 해주어야 하며, 에러가 발생했을 때 어디서 문제가 생겼는지 추적하기 어렵다.
- 유지보수 어려움: 코드 수정이나 기능 추가 시 복잡한 중첩 구조 때문에 실수를 유발하기 쉽다.
JavaScriptjs
프로미스(Promise)#
프로미스는 비동기 작업의 최종 완료(또는 실패)와 그 결과 값을 나타내는 객체 이다.
프로미스는 다음 세 가지 상태중 하나를 갖는다.
- 대기(Pending): 초기 상태, 비동기 작업이 아직 완료되지 않음
- 이행(Fullfilled): 비동기 작업이 성공적으로 완료됨. 결과 값을 가짐
- 거부(Rejected): 비동기 작업이 실패함. 실패 이유(에러)를 가짐. 프로미스는 한번 상태가 결정되면(이행 또는 거부) 더 이상 변하지 않는(settled) 특징을 갖는다.
JavaScriptjs
프로미스 체이닝(Promise Chaining)#
프로미스 체이닝은 프로미스의 .then()
또는 .catch()
메서드가 항상 새로운 프로미스를 반환하는 특성을 이용하여 여러 비동기 작업을 순차적으로 연결하는 기법이다. .then()
핸들러는 이전 프로미스가 이행(fullfill)되면 호출되며, 그 결과 값을 받아 다음 작업을 수행하고, 또 다른 값이나 새로운 프로미스를 반환하여 체인을 이어갈 수 있다.
JavaScriptjs
프로미스 체이닝은 비동기 코드의 흐름을 논리적이고 읽기 쉽게 만들어 콜백 지옥 문제를 해결하는 핵심적인 방법이다.
프로미스 메서드#
Promise.all()
: 여러 프로미스를 동시에 실행하고 모두 완료될 때까지 기다린다.Promise.race()
: 가장 먼저 완료되는 프로미스의 결과를 반환한다.Promise.allSettled()
: 모든 프로미스가 처리될 때까지 기다리고, 각 프로미스의 상태와 값을 반환한다.Promise.any()
: 가장 먼저 성공적으로 완료된 프로미스의 결과를 반환한다.
Async/Await#
async
/await
는 프로미스를 기반으로 동작하는 비동기 처리 문법으로, 비동기 코드를 마치 동기 코드처럼 더 읽기 쉽고 간결하게 작성할 수 있도록 도와주는 문법이다.
-
async
함수: 함수 선언 앞에async
키워드를 붙여 정의한다.async
함수는 항상 프로미스를 반환한다. 함수 본문에서 명시적으로 프로미스를 반환하지 않더라고, 반환 값은 자동으로Promise.resolve()
로 감싸져 프로미스로 반환된다. -
await
연산자:async
함수 내부에서만 사용할 수 있다.await
뒤에는 주로 프로미스가 오며, 해당 프로미스가 처리될 때까지async
함수의 실행을 일시 중지시킨다.. 프로미스가 이행되면await
표현식은 그 결과 값을 반환하고, 프로미스가 거부되면 에러를 던진다(throw
)
async
/await
에서의 에러 핸들링#
try...catch
문을 사용하여 처리한다.
try
블록 안에await
를 포함한 비동기 코드를 작성한다.- 만약
await
한 프로미스가 거부되면, 에러가 발생(throw)하고 제어 흐름은 즉시 해당catch
블록으로 이동한다. catch
블록에서는 발생한 에러 객체를 받아 로깅, 사용자 알림, 대체 로직 수행 등 필요한 에러 처리를 수행할 수 있다.
JavaScriptjs
병렬처리를 위한 성능 향상#
async
/await
는 코드를 간결하게 만들지만 await
는 코드 실행을 멈추기 때문에, 서로 의존성이 없는 여러 개의 비동기 작업을 순차적으로 await
하면 불필요하게 실행 시간이 길어질 수 있다.
JavaScriptjs